<template>
  <div :class="[className]" :style="style"></div>
</template>
<script>
import { config, domGetStyle, throttle, loadRemote } from '@vimi/utils-tool'

import ThemeDark from './theme/dark'
import ThemeMacarons from './theme/macarons'
import ThemeShine from './theme/shine'
import ThemeVintage from './theme/vintage'
import ThemeGreen from './theme/green'

export default {
  name: 'ElEcharts',
  components: {},
  props: {
    height: {
      type: [Number, String],
      default: 280,
    },
    width: {
      type: [Number, String],
      default: '100%',
    },
    className: String,
    option: Object,
    theme: String,
    mapUrl: String,
    mapName: String,
    loading: Boolean,
    // echarts原生回调事件, 格式{click: cb1, mouseover: cb2}
    eventListener: {
      type: Object,
      default: () => {
        return {}
      },
    },
  },
  data() {
    return {
      url: `${config.baseUrl}/libs/echarts/4.9.0/echarts.min.js`,
      chart: null,
    }
  },
  computed: {
    style() {
      return {
        height: this.height + 'px',
        width: this.width + (typeof this.chartWidth === 'string' ? '' : 'px'),
      }
    },
    scalability() {
      return !this.width || this.width === '100%'
    },
    heightNumber() {
      return Number.parseFloat(this.height) || 280
    },
  },
  watch: {
    option: {
      handler() {
        this.setOption()
      },
      deep: true,
    },
    loading(val) {
      this.toggleLoading(val)
    },
    height() {
      this.chartResizeHeight()
    },
  },
  created() {},
  activated() {
    this.resize()
  },
  mounted() {
    this.loadScript()
  },
  methods: {
    loadScript() {
      loadRemote(this.url, 'echarts').then(() => {
        this.registerTheme()
        this.registerMap().finally(() => {
          this.initChart()
        })
      })
    },

    initChart() {
      this.$nextTick(() => {
        /* eslint-disable new-cap */
        this.chart = new window.echarts.init(this.$el, this.theme)
        this.resize()

        // onInit 属性用于向上传递图表对象（无法保证在nextTick回调完成初始化，因为或能还要加载script脚本）
        this.$emit('onInit', this.chart, this.option)

        // 注册echarts原生事件
        Object.keys(this.eventListener).forEach(key => {
          this.addChartEventListener(key, this.eventListener[key])
        })
        window.addEventListener('resize', this.throttleChartResize)

        this.toggleLoading(this.loading)
        this.setOption()
        /* eslint-enable new-cap */
      })
    },

    throttleChartResize: throttle(200, function () {
      this.resize()
    }),

    setOption() {
      if (!this.chart || !this.option) {
        return
      }
      this.chart.clear()
      this.chart.setOption(this.option)
    },

    registerTheme() {
      window.echarts.registerTheme('dark', ThemeDark)
      window.echarts.registerTheme('macarons', ThemeMacarons)
      window.echarts.registerTheme('shine', ThemeShine)
      window.echarts.registerTheme('vintage', ThemeVintage)
      window.echarts.registerTheme('green', ThemeGreen)
    },

    chartResizeHeight() {
      if (!this.chart) {
        return
      }
      this.chart.resize({ height: this.heightNumber })
    },

    addChartEventListener(e, cb) {
      if (!this.chart || !cb) {
        return
      }

      // 通过zr，让图表可以点击空白处来响应事件
      this.chart.getZr().on(e, params => {
        const pointInPixel = [params.offsetX, params.offsetY]
        if (this.chart.containPixel('grid', pointInPixel)) {
          const offsets = this.chart.convertFromPixel(
            {
              seriesIndex: 0,
            },
            [params.offsetX, params.offsetY]
          )
          if (offsets && offsets.length) {
            cb(offsets[0])
          }
        }
      })
    },

    getChart() {
      return this.chart
    },

    toggleLoading(isLoading) {
      if (!this.chart) {
        return
      }

      if (isLoading) {
        this.chart.showLoading()
      } else {
        this.chart.hideLoading()
      }
    },

    getStyleNum(el, styleName) {
      const val = domGetStyle(el, styleName) // '15px'
      return Number.parseFloat(val) || 0
    },

    getParentWidth(el, paddingX = 0) {
      if (!el.parentNode) {
        return 0
      }
      const pl = this.getStyleNum(el.parentNode, 'paddingLeft')
      const pr = this.getStyleNum(el.parentNode, 'paddingRight')
      const { offsetWidth } = el.parentNode
      if (offsetWidth) {
        return offsetWidth - paddingX - pl - pr
      }
      return this.getParentWidth(el.parentNode, pl + pr)
    },

    resize() {
      if (!this.scalability || !this.chart) {
        return
      }

      // 这里设置后，当页面的滚动条出现时，不会减少宽度，所以会有17px(chrome滚动条宽度)的误差
      // 如果需要避免此误差，需要监听浏览器滚动条出现与隐藏事件
      // 暂时由上层页面手动执行resize处理
      if (this.$el) {
        this.chartResize()
      } else {
        this.$nextTick(() => this.chartResize())
      }
    },

    chartResize() {
      const width = this.getParentWidth(this.$el)
      this.$el.style.width = `${width}px`
      this.chart.resize()
    },

    registerMap() {
      return new Promise((resolve, reject) => {
        if (this.mapUrl && this.mapName) {
          this.$http
            .get(this.mapUrl)
            .then(res => {
              window.echarts.registerMap(this.mapName, res.data)
              resolve()
            })
            .catch(e => {
              reject(e)
            })
        } else {
          resolve()
        }
      })
    },
  },
  beforeDestroy() {
    window.removeEventListener('resize', this.throttleChartResize)
    if (this.chart) {
      this.chart.dispose() // 销毁图表实例
    }
  },
}
</script>
<style scoped></style>
